HAMPU: クライアント暗号化機能
HAMPU: クライアント暗号化機能
ユーザーは信頼できない開発者をどう信頼するだろうか?全てを暗号化することだ
まあ実際にはそうは言ってもLINEですら信頼しない人もいるわけなので感情に完璧はあり得ない
匿名化したデータの統計は利用できるような規約にしたい。島中はこんなもん、みたいなデータがみれたら面白いと思う(ブースの情報を持たないと実現不可能)
設計検討
GPT-5.icon
結論: KISSで始めるなら「E2EE(エンドツーエンド暗号)+ ドキュメント毎のDEK(データ鍵)+ 受信者ごとの鍵ラップ(再暗号化)」。共有はDEKの再ラップで実現、開発者はユーザーの明示オプトイン時にだけ一時的にDEKを受け取る。匿名統計はクライアント集計または軽い差分プライバシで送る。
この設計が合う理由:HAMPUの要件「開発者は見れない/共有相手だけ見れる/匿名統計を扱いたい」に一致し、段階導入でKISSから始められる。まずはURLフラグメント鍵のMVP→受信者ごとのDEK再ラップに移行が現実的。
目的別の最小構成
開発者に見えなくする
ブラウザで暗号化→サーバは常に暗号文のみ保存(D1/R2/DOは暗号文運搬だけ)
各ドキュメントに32BのDEK(AES-GCM)を生成し本文を暗号化
DEKは閲覧許可ユーザーの公開鍵ごとに「ラップ(DEKをその人の鍵で暗号化)」してサーバに添付保存
開発者鍵でラップされたDEKは標準では作らない(=開発者は読めない設計) 
シェア先には見えるようにする
共有時、クライアントが受信者の公開鍵を取得し、DEKをその公開鍵でラップして共有リストに追加
超KISS案(MVP/一時共有): 「URLフラグメントに鍵を載せる#key=…」。サーバは見えない(フラグメントは送信されない)。ただし流出・取り消し不可なので恒常共有には非推奨
取り消し・権限制御が要る場合はDEKローテーション(該当受信者のラップを外し、新DEKで本文再暗号→残り受信者へ再ラップ)
ユーザーがオプトインしたら開発者が扱えるようにする
「サポート用一時アクセス」ボタンで、クライアントがDEKを開発者公開鍵にラップして発行
メタデータに有効期限・監査ログ・発行者IDを付ける(TTL後は無効扱い)
解除はDEKローテーションで即時反映(開発者向けラップを削除)
匿名化された状態でもデータを扱いたい
KISS案A: クライアントで集計してから送信(例: 売上の時系列ヒストグラムや合計のみ送る)
KISS案B: ランダム応答(Local DP)。各イベントを一定確率で反転して送る(ε/プライバシ予算をUIで明示)
KISS案C: k匿名バケット化(例えば時刻を5分刻み、数量を区間化)
非KISS案(参考): TEEやFHEは重いので後回し
鍵・IDまわり(実装の素朴な選択肢)
ユーザー鍵
永続: X25519(ECDH)/Ed25519(署名)またはWebCrypto標準のP-256(ECDH)/Ed25519
生成と保管: SubtleCryptoで生成→秘密鍵はIndexedDBに保存、復元用にパスフレーズでArgon2id/PBKDF2した鍵で暗号化してエクスポート
ドキュメント鍵(DEK)
生成: 32Bランダム(AES-GCM用)
保管: サーバにはciphertextと「recipientId→wrappedDEK」マップを保存
共有リンク(MVP)
path部分=ドキュメントID、URLフラグメント=#key=BASE64(DEK)(サーバには見えない)
恒常共有へ移行時は受信者公開鍵に再ラップしてリンクは鍵無しに
Cloudflare/Workers/D1/DOへの落とし込み(最小差分)
D1: documents(id, ciphertext, nonce, algo, createdAt, updatedAt)
D1: doc_keys(docId, recipientUserId, wrappedDEK, alg, kid, expiresAt)
DO: 整合性のある更新APIだけ提供(暗号処理は全部ブラウザ)
R2: 大きな添付ファイルはクライアントで分割暗号(AES-GCM-SIV等)して保存
ブラウザ: SubtleCrypto(AES-GCM/ECDH/HKDF)、鍵はIndexedDB、共有時のみネット経由で受信者公開鍵を取得
ユーザー体験の最小フロー
作成
生成: DEK→本文をAES-GCMで暗号化→自分の公開鍵でDEKをラップ→アップロード
表示
自分の秘密鍵でwrappedDEKを復号→本文復号→描画
共有
相手の公開鍵取得→DEKを再ラップ→doc_keysに追加→通知
取り消し
新DEK生成→本文再暗号→残りの許可者へ新DEKを再ラップ→旧DEKと旧ciphertextを無効化
開発者サポート(明示オプトイン時のみ)
「一時アクセスを付与」→開発者公開鍵向けwrappedDEKを作成(TTL/監査付き)→終了時にDEKローテーション
脅威モデルと限界(正直ポイント)
端末侵害時は読まれる(端末安全性は前提)
URLフラグメント方式は楽だが流出・再共有の制御ができない
サーバはメタデータ(アクセス頻度・サイズ)を観測できる(暗号でも隠せない領域)
匿名データの扱い(具体例)
クライアント集計で送るだけ
例: 5分刻みの販売数ヒストグラム、合計在庫、中央値などの統計量のみPOST
ランダム応答(Local DP)
例: p=0.1で販売イベントを「ある」を「ない」に、q=0.1で「ない」を「ある」に反転して送信。サーバ側は補正推定
k匿名化
例: 訪問数がk未満の時間帯はまとめて「その他」バケットにする
注意: 生データのハッシュ化のみは再識別リスクが高いので非推奨(下の「倫理的でない案」参照)
段階導入プラン(KISS→堅牢へ)
Phase 1: URLフラグメント鍵の一時共有(実装3日で到達可能な最低限)
Phase 2: 受信者ごとのDEK再ラップ方式(取り消し・監査)
Phase 3: サポート用一時アクセス(ユーザー明示のオプトイン/TTL/ログ)
Phase 4: 匿名統計(クライアント集計 + Local DPオプション)
アルゴリズムとパラメータの素朴な推奨
本文暗号: AES-256-GCM(12B nonce, 16B tag)
鍵交換: X25519-ECDH + HKDF-SHA256でDEKラップ鍵を派生→AES-KW or AES-GCMでラップ
乱数: window.crypto.getRandomValues
鍵導出: Argon2id(またはPBKDF2-SHA256, 100k+反復)でパスフレーズ→鍵
倫理的でない/リスク高の代替案(現実にあるが推奨しない)
サーバ側鍵エスクロー(バックドア)
ユーザー秘密鍵のコピーをサーバの公開鍵でも暗号化して保管し、運営が復号可能にする
規約で同意を取っても、内部不正・漏洩リスクが高い
擬似匿名(ハッシュ化だけ)
生データをソルト付きハッシュで「匿名化」して扱う手法
外部データと突合で再識別されうる。統計目的でもLDP等のノイズ付与なしは危険
共有時の鍵を平文でAPI経由配送
ログやミドルボックスに残るリスク大。URLフラグメントですら回避するのに逆行